/*
 *  BaseCat.cpp
 *  Untitled
 *
 *  Created by Guo Wei Tan on 1/20/14.
 *  Copyright 2014 __MyCompanyName__. All rights reserved.
 *
 */

#include "BaseCat.h"
#include "Cats/CatSpriteLoader.h"
#include "Cats/CatStates/CatState_Base_Chase.h"
#include "Cats/CatStates/CatState_Base_Obstacle.h"
#include "Cats/CatStates/CatState_Base_Attack.h"
#include "Cats/CatStates/CatState_Base_Flank.h"
#include "Cats/CatStates/CatState_Base_Skill.h"
#include "Cats/CatStates/CatState_Base_Injured.h"
#include "Cats/CatStates/CatState_Base_Dying.h"
#include "Cats/CatStates/CatState_Base_Dead.h"

#include "Character/Character.h"
#include "Map/GameMap.h"

BaseCat::BaseCat()
{
	//BASE VALUES
	//animation
	RECT_BUFFER_RADIUS = 20;

	//movement
	VARYING_SPEED = 20;
	BASE_SPEED = 180;
	BASE_SPEED_MULTIPLIER = 1;

	//attack
	BASE_ATTACK_RANGE_GRID = 3;
	BASE_ATTACK_DAMAGE = 1;
	BASE_ATK_SPEED = BASE_SPEED * 2;

	//obstacle/stop
	BASE_OBSTACLE_HIT_CHANCE = 10;
	SLOW_SPEED_MULTIPLIER = 2;
	SLOW_DURATION = 4;

	//jump
	BASE_JUMP_HEIGHT = 30;
	BASE_JUMP_SPEED = BASE_SPEED;

	//skill
	SKILL_AVAILABLE = false;
	BASE_SKILL_COOLDOWN = 10;

	//health
	BASE_HEALTH = 3;

	//invul timer
	BASE_INVUL_DURATION = 1;

	//cat type
	CAT_TYPE = PUNK_CAT;
	//cat state
	_currentCatState = CAT_CHASE;

	//set immunities
	setImmunity();
}

void BaseCat::initCat()
{
	//INITIALIZATION
	GameMap* pMapRef = GameMap::getInstance();

	//ANIMATION
	_isFlipped = false;
	_pImmunityEffectLayer = 0;
	_pStatusEffectsLayer = 0;

	//MOVEMENT
	// Add a different speed to each cat
	_speedX = _speedY = (BASE_SPEED * pMapRef->getScaleFactor()) + arc4random()% (2*VARYING_SPEED) - VARYING_SPEED;
	_speedMultiplier = _directorSpeedMultiplier = BASE_SPEED_MULTIPLIER;
	_isSlowed = false;
	_slowMultiplier = 1.0;

	//ATTACK
	_attackRange = BASE_ATTACK_RANGE_GRID * pMapRef->getPixelsPerGrid();
	_attackSpeedX = _attackSpeedY = BASE_ATK_SPEED * pMapRef->getScaleFactor();

	//OBSTACLE/SLOW
	_obstacleHitChance = BASE_OBSTACLE_HIT_CHANCE;

	//SKILL
	_skillCoolDown = _skillCoolDownTimer = BASE_SKILL_COOLDOWN;
	_skillReady = SKILL_AVAILABLE;

	//HEALTH
	_health = BASE_HEALTH;

	//INJURY
	_healthReduction = 0;
	_injuryType = INJURY_NORMAL;

	//IMMUNITY
	_immunityTimer = 0;

	//TOUCH
	_isSwiped = false;

	//STATE
	initStates();
	_futureCatState = EMPTY_CAT_STATE;

}

/*!
    @function initStates
    @result		initialize the states of the cat.
    			TO BE OVERRIDEN BY CHILDREN CLASSES
 */
void BaseCat::initStates()
{
	_myCatStates = new CatState*[CAT_STATE_NUM];
	_myCatStates[CAT_CHASE] = new CatState_Base_Chase(this);
	_myCatStates[CAT_OBSTACLE] = new CatState_Base_Obstacle(this);
	_myCatStates[CAT_ATTACK] = new CatState_Base_Attack(this);
	_myCatStates[CAT_FLANK] = new CatState_Base_Flank(this);
	_myCatStates[CAT_SKILL] = new CatState_Base_Skill(this);
	_myCatStates[CAT_INJURED] = new CatState_Base_Injured(this);
	_myCatStates[CAT_DYING] = new CatState_Base_Dying(this);
	_myCatStates[CAT_DEAD] = new CatState_Base_Dead(this);

	_myCatStates[CAT_CHASE]->onEnter();
	_currentCatState = CAT_CHASE;
	_previousCatState = CAT_CHASE;
}

BaseCat::~BaseCat()
{
	for (int i = 0; i < CAT_STATE_NUM; i++)
		delete _myCatStates[i];
	delete [] _myCatStates;

	_pImmunityEffectLayer = 0;
	_pStatusEffectsLayer = 0;

	if (_numOfImmunities > 0)
		delete _immunityList;

	stopImmuneEffectLayer();
	stopStatusEffectsLayer();
}

/*!
    @function createCat
    @result		returns a cat with loaded sprites
 */
BaseCat* BaseCat::createCat()
{
	BaseCat *cat = new BaseCat();

	if( cat && cat->initWithSpriteFrame(CatSpriteLoader::getSpriteFrame(cat->getCatType())))
	{
		cat->setScale(GameMap::getInstance()->getScaleFactor());
		cat->initCat();
		cat->autorelease();
		return cat;
	}

	CC_SAFE_DELETE(cat);
	return NULL;
}

/*!
    @function update
    @param		timeDiff:float
				time difference between the last update call
    @result		base on the state the cat is in, call corresponding functions
 */
void BaseCat::update(float timeDiff)
{
	//reduce health
	if (_healthReduction > 0)
	{
		_health -= _healthReduction;
		_healthReduction = 0;
	}

	//check if theres any change in state
	changeState();

	//update the skill cool down if possible
	if (SKILL_AVAILABLE)
		reduceCoolDown(timeDiff);

	//update slow timer if possible
	if (_isSlowed)
		reduceSlowTimer(timeDiff);

	reduceImmuneTimer(timeDiff);

	//update state
	_myCatStates[_currentCatState]->update(timeDiff);

}

void BaseCat::startCoolDownTimer()
{
	if (_skillReady)
	{
		_skillReady = false;
		_skillCoolDownTimer = _skillCoolDown;
	}
}

/*!
    @function reduceCoolDown
    @param		timeDiff:float
				time difference since the last call
    @result		reduces the cool down timer of the skill.
    			sets _skillReady to be true when cool down reaches 0.
    			resets the timer when it reaches 0.
 */
void BaseCat::reduceCoolDown(float timeDiff)
{
	//if already cooled down
	if (_skillReady)
		return;

	_skillCoolDownTimer -= timeDiff;
	//skill ready for use
	if ( _skillCoolDownTimer <= 0)
	{
		_skillReady = true;
		//reset the timer
		_skillCoolDownTimer = _skillCoolDown;
	}
}

void BaseCat::reduceSlowTimer(float timeDiff)
{
	_slowEffectTimer -= timeDiff;
	if (_slowEffectTimer <= 0)
	{
		_isSlowed = false;
		_speedMultiplier /= _slowMultiplier;
		this->stopStatusEffectsLayer();
	}
}

void BaseCat::reduceImmuneTimer(float timeDiff)
{
	if (_immunityTimer > 0)
	{
		_immunityTimer -= timeDiff;
		if (_immunityTimer <= 0)
		{
			this->stopImmuneEffectLayer();
			_immunityTimer = 0;
		}
	}
}
/*!
    @function reduceHealth
    @param		healthReduction:int
				the amount of health to reduce from the cat
	@param		injuryType:EnumCatInjury
				the type of injury that caused this reduce in health
    @result		reduces the specified amount of health from the cat.
    			goes into CAT_INJURED state
 */
void BaseCat::reduceHealth(int healthReduction, EnumCatInjury injuryType)
{
	//if cat is in non-touchable state, exit function
	if (_currentCatState == CAT_INJURED || isDead())
		return;

	//check for skill immunity and play animation if needed
	if (checkImmunity(injuryType))
	{
		this->stopImmuneEffectLayer();
		this->getImmuneEffectLayer()->runAction(CatSpriteLoader::getCatStatusAnimation(CAT_ANIM_IMMUNE));
		_immunityTimer = 0.5;
		//do not damage this cat, exit function
		return;
	}

	//if the new injury is normal, and the current injury is a skill injury
	//Skill injury precedence > normal injury
	if (injuryType == INJURY_NORMAL && _injuryType != INJURY_NORMAL)
	{
		//CCLog("normal attack can't override skill attack!");
		return;
	}
	//set health reduction
	_healthReduction = healthReduction;

	//set injury type
	_injuryType = injuryType;

	//set next state
	_futureCatState = CAT_INJURED;
}

/*!
    @function addSlowEffect
    @param		slowDuration:float
				the duration to keep the cat slowed
	@param		slowMultiplier:float
				_speedMultiplier *= slowMultiplier
    @result		reduces the speed of the cat according to the multiplier and starts the count down timer
 */
void BaseCat::addSlowEffect(float slowDuration, float slowMultiplier)
{
	if (!_isSlowed)
	{
		_slowMultiplier = slowMultiplier;
		_speedMultiplier *= _slowMultiplier;
		_isSlowed = true;
	}
	//restart slow timer if constantly being slowed
	_slowEffectTimer = slowDuration;
}

/*!
    @function move
    @param		timeDiff:float
				time difference since the last call
    @result		moves the cat based on its current speed and multiplier.
 	 	 	 	returns the amount moved by the cat
 */
float BaseCat::move(float timeDiff)
{
	float moveAmount = ((_speedX * timeDiff) * _speedMultiplier * _directorSpeedMultiplier) - Character::getInstance()->getXSpeed(timeDiff);
	//update position to move towards left of screen based on speed (LEFT SIDE, MINUS)
	this->setPositionX(this->getPositionX() - moveAmount);

	return moveAmount;
}

void BaseCat::flipCatSprite()
{
	_isFlipped = !_isFlipped;
	this->setScaleX( this->getScaleX() * -1 );
}

/*!
    @function getRect
    @result		returns a CCRect based on this sprite with a reduced size according to the RECT_BUFFER_RADIUS.
    			mainly used for a more accurate collision detection
 */
CCRect BaseCat::getRect()
{
	return CCRectMake(this->getPosition().x - (this->getContentSize().width/2) + RECT_BUFFER_RADIUS,
			this->getPosition().y - (this->getContentSize().height/2) + RECT_BUFFER_RADIUS,
			this->getContentSize().width - RECT_BUFFER_RADIUS *2,
			this->getContentSize().height - RECT_BUFFER_RADIUS *2);
}

int BaseCat::getAttackRange()   {
	return _attackRange;
}

void BaseCat::setAttackRange(int attackRange) {
	_attackRange = attackRange;
}

EnumCatState BaseCat::getCurrentCatState()   {
	return _currentCatState;
}

void BaseCat::setNextCatState(EnumCatState newCatState) {
	//save future state
	_futureCatState = newCatState;
}

void BaseCat::changeState(){
	if (_futureCatState == EMPTY_CAT_STATE)
		return;

	//change state based on the current state
	if (_currentCatState != _futureCatState)
	{
		//check for INJURED state (pause state)
		if (_futureCatState == CAT_INJURED)
			_myCatStates[_currentCatState]->onPause();
		else
			_myCatStates[_currentCatState]->onExit();

		_myCatStates[_futureCatState]->onEnter();

		//dont store injured state
		if (_currentCatState != CAT_INJURED)
			_previousCatState = _currentCatState;
		_currentCatState = _futureCatState;
		_futureCatState = EMPTY_CAT_STATE;
	}
}

EnumCatState BaseCat::getPreviousCatState(){
	return _previousCatState;
}

void BaseCat::setPreviousCatState(EnumCatState previousCatState){
	_previousCatState = previousCatState;
}

bool BaseCat::isDead() {
	if (_health <= 0 || _currentCatState == CAT_DYING || _currentCatState == CAT_DEAD)
		return true;
	return false;
}

bool BaseCat::isInjured(){
	return _currentCatState == CAT_INJURED;
}

int BaseCat::getHealth()   {
	return _health;
}

int BaseCat::getHealthReduction() {
	return _healthReduction;
}

EnumCatInjury BaseCat::getInjuryType()   {
	return _injuryType;
}

void BaseCat::setInjuryType(EnumCatInjury injuryType) {
	_injuryType = injuryType;
}

int BaseCat::getObstacleHitChance()   {
	return _obstacleHitChance;
}

void BaseCat::setObstacleHitChance(int obstacleHitChance) {
	_obstacleHitChance = obstacleHitChance;
}

bool BaseCat::isSkillReady()   {
	return _skillReady;
}

void BaseCat::setSkillReady(bool skillReady) {
	_skillReady = skillReady;
}

float BaseCat::getSpeedMultiplier()   {
	return _speedMultiplier;
}

float BaseCat::getDirectorSpeedMultiplier(){
	return _directorSpeedMultiplier;
}

void BaseCat::setSpeedMultiplier(float speedMultiplier) {
	_speedMultiplier = speedMultiplier;
}

void BaseCat::setDirectorSpeedMultiplier(float speedMultiplier) {
	_directorSpeedMultiplier = speedMultiplier;
}

float BaseCat::getSpeedX()   {
	return _speedX;
}

void BaseCat::setSpeedX(float speedX) {
	_speedX = speedX;
}

float BaseCat::getSpeedY()   {
	return _speedY;
}

void BaseCat::setSpeedY(float speedY) {
	_speedY = speedY;
}

float BaseCat::getAttackSpeedX() {
	return _attackSpeedX;
}

float BaseCat::getAttackSpeedY(){
	return _attackSpeedY;
}

int BaseCat::getBaseAtkSpeed()   {
	return BASE_ATK_SPEED;
}

int BaseCat::getBaseAttackDamage()   {
	return BASE_ATTACK_DAMAGE;
}

int BaseCat::getBaseAttackRangeGrid()   {
	return BASE_ATTACK_RANGE_GRID;
}

int BaseCat::getBaseHealth()   {
	return BASE_HEALTH;
}

int BaseCat::getBaseJumpHeight()   {
	return BASE_JUMP_HEIGHT;
}

int BaseCat::getBaseJumpSpeed()   {
	return BASE_JUMP_SPEED;
}

int BaseCat::getBaseObstacleHitChance()   {
	return BASE_OBSTACLE_HIT_CHANCE;
}

int BaseCat::getBaseSkillCooldown()   {
	return BASE_SKILL_COOLDOWN;
}

int BaseCat::getBaseSpeed()   {
	return BASE_SPEED;
}

float BaseCat::getBaseSpeedMultiplier()   {
	return BASE_SPEED_MULTIPLIER;
}

EnumCatType BaseCat::getCatType()   {
	return CAT_TYPE;
}

bool BaseCat::isSkillAvailable()   {
	return SKILL_AVAILABLE;
}

void BaseCat::setSkillAvailable(bool skillAvailable) {
	SKILL_AVAILABLE = skillAvailable;
}

int BaseCat::getSlowDuration()   {
	return SLOW_DURATION;
}

float BaseCat::getSlowSpeedMultiplier()   {
	return SLOW_SPEED_MULTIPLIER;
}

int BaseCat::getVaryingSpeed()   {
	return VARYING_SPEED;
}

float BaseCat::getBaseInvulDuration(){
	return BASE_INVUL_DURATION;
}

void BaseCat::setIsSwiped(bool isSwiped){
	_isSwiped = isSwiped;
}

bool BaseCat::isSwiped(){
	return _isSwiped;
}

bool BaseCat::isFlipped(){
	return _isFlipped;
}

bool BaseCat::isSlowed(){
	return _isSlowed;
}

void BaseCat::stopStatusEffectsLayer(){
	if (_pStatusEffectsLayer == 0)
		return;
	_pStatusEffectsLayer->stopAllActions();
	_pStatusEffectsLayer->removeFromParent();
	_pStatusEffectsLayer = 0;
}

CCSprite* BaseCat::getStatusEffectsLayer(){
	//create a new effects layer
	if (_pStatusEffectsLayer == 0)
	{
		_pStatusEffectsLayer = CCSprite::create();
		_pStatusEffectsLayer->setPositionX(this->boundingBox().size.width/4);
		_pStatusEffectsLayer->setPositionY(this->boundingBox().size.height/4);
		this->addChild(_pStatusEffectsLayer);
	}
	return _pStatusEffectsLayer;
}

void BaseCat::stopImmuneEffectLayer(){
	if (_pImmunityEffectLayer == 0)
		return;
	_pImmunityEffectLayer->stopAllActions();
	_pImmunityEffectLayer->removeFromParent();
	_pImmunityEffectLayer = 0;
}

CCSprite* BaseCat::getImmuneEffectLayer(){
	//create a new blink effect layer
	if (_pImmunityEffectLayer == 0)
	{
		_pImmunityEffectLayer = CCSprite::create();
		_pImmunityEffectLayer->setPositionX(this->boundingBox().size.width/4);
		_pImmunityEffectLayer->setPositionY(this->boundingBox().size.height/4);
		this->addChild(_pImmunityEffectLayer);
	}
	return _pImmunityEffectLayer;
}

void BaseCat::setImmunity(EnumCatInjury immunity1, EnumCatInjury immunity2, EnumCatInjury immunity3)
{
	bool oneIsValid, twoIsValid, threeIsValid;
	oneIsValid = twoIsValid = threeIsValid = false;
	_numOfImmunities = 0;

	//cannot be immune to normal injury for all cats
	//check if the immunities given are valid
	if (immunity1 != INJURY_NORMAL)
	{
		oneIsValid = true;
		_numOfImmunities++;
	}

	if (immunity2 != INJURY_NORMAL)
	{
		twoIsValid = true;
		_numOfImmunities++;
	}

	if (immunity3 != INJURY_NORMAL)
	{
		threeIsValid = true;
		_numOfImmunities++;
	}

	//create immunity lsit
	if (_numOfImmunities > 0)
		_immunityList = new EnumCatInjury[_numOfImmunities];

	//assign the immunities into the array
	if (oneIsValid)
		_immunityList[0] = immunity1;

	if (twoIsValid)
	{
		if (oneIsValid)
			_immunityList[1] = immunity2;
		else
			_immunityList[0] = immunity2;
	}

	if (threeIsValid)
	{
		if (_numOfImmunities == 3)
			_immunityList[2] = immunity3;
		else if (_numOfImmunities == 2)
			_immunityList[1] = immunity3;
		else
			_immunityList[0] = immunity3;
	}

}

bool BaseCat::checkImmunity(EnumCatInjury incomingInjury)
{
	for (int i = 0; i < _numOfImmunities; i++)
	{
		if (_immunityList[i] == incomingInjury)
			return true;
	}

	return false;
}

